// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#ifndef VECTOR_H
#define VECTOR_H

#include <QDebug>
#include <QtOpenGL/QtOpenGL>

/**
 * \class Vector
 * \brief Represents a 3D vector object (x,y,z) using glDouble.
 * \author Kosta Gaitanis
 */
class Vector
{
public:
    static const Vector ORTHO_X;     /// Predefined X-axis ort. (1,0,0)
    static const Vector ORTHO_Y;     /// Predefined Y-axis ort. (0,1,0)
    static const Vector ORTHO_Z;     /// Presefined Z-axis ort. (0,0,1)
    static const Vector ZERO;        /// Predefined ZERO-vector.(0,0,0)

    /**
     * Constructs an empty vector.
     * The function isEmpty() will return true until the coordinates of this
     * vector have been set in any way.
     *
     * An empty vector is not the same a the vector (0,0,0) !
     * An empty vector is an UNDEFINED vector.
     */
    Vector();

    /**
     * Constructs a new vector with the given parameters.
     * @param x,y,z the coordinates.
     */
    Vector(double x, double y, double z);

    /**
     * Creates a new vector with the same coordinates as the other vector.
     * @param other The vector object to be copied.
     */
    Vector(const Vector& other);

    /**
     * Operator ==
     * An empty vector is only equal to another empty vector
     * Two non-empty vectors are equal only if all their coordinates
     * are the same (up to a certain precision)
     *
     * Eg.
     *  Vector()      == Vector()                   => true    (two empty vectors are always equal)
     *  Vector()      == Vector(0,0,0)              => false   (empty vector is never equal to non-empty)
     *  Vector()      == Vector(1,2,3)              => false   (empty vector is never equal to non-empty)
     *  Vector(1,2,3) == Vector(1,2,3)              => true    (same coordinates)
     *  Vector(1,2,3) == Vector(1,2,4)              => false   (coordinates are different)
     *  Vector(1,0,0) == Vector(1.0000000001, 0,0)  => true    (difference is too small)
     *
     * @param other The vector to be compared to this one.
     * @return True if all coordinates are the same between this vector
     *         and the other vector.
     */
    bool operator ==(const Vector& other) const;

    /**
     * Operator !=
     *
     * same as calling !(v1 == v2).
     */
    bool operator !=(const Vector& other) const;

    /**
     * Operator =
     * This vector's coordinates are set to the same coordinates as the other vector.
     * @param other The vector whose parameters will be copied on this one.
     * @return a reference to this vector.
     */
    Vector& operator =(const Vector& other);

    /**
     * Operator +
     * Adds two vectors
     * @param other The right hand term of the addition.
     * @return A new vector = this + other
     */
    Vector operator +(const Vector& other) const;

    /**
     * Operator +
     * Subtracts two vectors
     * @param other The right hand term of the subtraction.
     * @return A new vector = this - other
     */
    Vector operator -(const Vector& other) const;

    /**
     * @brief operator += adds a vector to this vector.
     * @param other The vector to be added to this one.
     * @return A reference to this vector.
     */
    Vector& operator +=(const Vector& other);

    /**
     * @brief operator -= subtracts a vector from this vector.
     * @param other The vector to be subrtacted from this one.
     * @return A reference to this vector.
     */
    Vector& operator -=(const Vector& other);

    /**
     * Scalar multiplication of two vectors.
     * @param v Reference to another vector.
     * @return Scalar product.
     */
    double operator *(const Vector &v) const;

    /**
     * Multiply vector by a number. This results in a vector that has the same
     * direction as the original but is smaller/bigger by factor f.
     * @param f Scale factor.
     * @return Scaled vector.
     */
    Vector operator *(double f) const;

    /**
     * @brief the vectorial product a x b where a and b are vectors.
     * @param other the right hand term of the vectorial product.
     * @return The vectorial product between this vector and other.
     */
    Vector operator ^(const Vector& other) const;

    /**
     * Returns a vector of the opposite direction.
     * @return Negated vector.
     */
    Vector operator -() const;

    /**
     * Modifies this vector's coordinates.
     * Same as calling setCoordinates(coordinates[0], coordinates[1], coordinates[2]).
     *
     * @param coordinates The new coordinates (x,y,z) in an array of GLdouble.
     */
    void setCoordinates(const GLdouble* coordinates);

    /**
     * Modifies this vector's coordinates
     * @param x,y,z The new coordinates.
     */
    void setCoordinates(double x, double y, double z);

    /**
     * @return The vector's coordinates as a GLdouble array (x,y,z).
     */
    const GLdouble *getGLCoordinates() const;

    /**
     * @return True if this vector is empty (no coordinates have been specified)
     * Only vectors constructed using the empty constructor Vector() are empty.
     * As soon as the setCoordinates() method is called on an empty vector,
     *this functions returns false.
     */
    bool isEmpty() const;

    /**
     * @return The x coordinate of this vector as a double.
     */
    double x() const;

    /**
     * @return The y coordinate of this vector as a double.
     */
    double y() const;

    /**
     * @return The z coordinate of this vector as a double.
     */
    double z() const;

    /**
     * Replaces the value of the x-coordinate.
     *
     * @param x the new x-coordinate.
     */
    void setX(double x);

    /**
     * Replaces the value of the y-coordinate.
     *
     * @param y the new y-coordinate.
     */
    void setY(double y);

    /**
     * Replaces the value of the z-coordinate.
     *
     * @param z the new z-coordinate.
     */
    void setZ(double z);

    /**
     * @return The length of this vector.
     */
    double getLength() const;

    /**
     * @return A new vector that has the same direction as this one but has a length
     *         of 1.0
     */
    Vector normalized() const;

    /**
     * Compares a and b and returns true if their values are close.
     * The approximation error is set by default to 0.00001.
     * (Use this function to compare double precision numbers because the
     * c++ implementation of operator== for doubles can have unexpected results)
     *
     * @param a a number (double precision).
     * @param b another number (double precision).
     * @param e The smallest decelable difference between two numbers.
     * @return True if the difference between the two numbers is smaller than e.
     */
    static bool compare(GLdouble a, GLdouble b, GLdouble e = 0.00001);

private:
    GLdouble m_coordinates[3];   /// The coordinates
    bool m_isEmpty;              /// True if no coordinates have been set yet.
};

/**
 * Allows printing the vector using qDebug() << vector
 * @param dbg The qDebug datastream.
 * @param vector The vector to be streamed in qDebug.
 */
QDebug operator <<(QDebug dbg, const Vector& vector);

#endif // VECTOR_H
